////////////////////////////////////////////////////////
//此文件放置一些工具类和函数
//为了让这些工具用的方便，直接把声明和实现都放在.hpp中
/////////////////////////////////////////////////////

#pragma once
#include <iostream>
#include <fstream>
#include <vector>
#include <unordered_map>
#include <boost/algorithm/string.hpp>
#include <sys/stat.h>
// #include <boost/filesystem.hpp>
#include <unistd.h>
#include <assert.h>
#include <fcntl.h>

#include <sys/time.h>
#include <sys/socket.h>

//得到时间戳的类
class Timeutil
{
public:
    static int64_t TimeStamp(){
    //为了不用创建对象也可以使用该函数，就设成static，直接用类::函数名来访问海曙
    //这里要用类来封装来这些函数只是一种方法，将获取时间戳的函数封装在一起以便调用
    //获取秒级时间戳,int32_t也可以，只是现在的时间戳已经快到了int32_t能表示的最大值了，所以用int64_t就行了
        struct timeval tv;
        gettimeofday(&tv,NULL);//获取时间戳的函数
        return tv.tv_sec;
    }

    static int64_t TimeStampUS(){
    //获取微秒级时间戳
        struct timeval tv;
        gettimeofday(&tv,NULL);
        return 1000*1000*tv.tv_sec + tv.tv_usec;
    }
};

enum Loglevel{
    //日志错误级别
    INFO,
    WARNING,
    ERROR,
    CRITIAL,
    DEBUG,
};

// LOG(INFO) << ""
inline std::ostream& Log(Loglevel level,const char* file,int line)
//为什么用inline，这只是给编译器的一个建议，为了将函数声明和定义在一个文件中，用inline
//防止多个文件引用这个文件，并且都定义了这个函数，那么一链接就会发生重定义的问题
//还可以用static来代替
{
    std::string prefix = "I";
    if(level == WARNING){
        prefix = "W";
    }
    if(level == ERROR){
        prefix = "E";
    }
    if(level == CRITIAL){
        prefix = "C";
    }
    if(level == DEBUG){
        prefix = "D";
    }
  
    //__FILE__ __LINE__
    std::cout << "[" << prefix << " " <<  Timeutil::TimeStamp() << " "<< file << ":" << line << "] ";
    return std::cout;//这个类型是不能够被拷贝的，所用返回值要用引用
}

#define LOG(level) Log(level,__FILE__,__LINE__)
//  __FILE__ //获取代码在哪个文件
//  __LINE__ //获取代码在该文件的第几行
//  如果这里不用宏，而是用函数，那么只会输出这个函数的行号，没办法动态捕捉到行号


class FileUtil{
public:
    //从文件描述符中读取一行
    //一行的界定标准是 \n \r \r\n
    //返回的line中是不包含界定符的
    //比如aaaaa\ncccccc
    //返回的就是aaaaa
    //不包含\n
    static int ReadLine(int fd,std::string& line){
        //服务器中的一个string对象每次请求都要给这个string对象进行赋值
        //所以需要在初始化阶段clear清除之前的内容
        //但是没有释放内存，可能会有内存泄露
        //比如一般情况下，str长度是100个字节
        //如果每次请求str的长度突然变成了1G
        //这样因为不会释放内存，就会发生内存泄露
        //line->clear(),并不会释放内存
        //可以把clear操作替换成如下操作
        //libine->clear();
        {
            std::string tmp;
            line.swap(tmp);
            //出了这个作用域之后tmp就自动调用析构函数西沟了
            //str就变为空字符串了
        }
        
        while(true){
            char c = '\0';
            ssize_t read_size = recv(fd,&c,1,0);//每次从fd中读取一个字符，放到c中
            if(read_size <= 0){
                return -1;
            }

            //将\r的情况转化为\n解决
            if(c == '\r'){
                recv(fd,&c,1,MSG_PEEK);//虽然从缓冲区里读了一个字符，但是缓冲区并没有把他删掉
                if(c == '\n'){
                    recv(fd,&c,1,0);//如果就是\n，为了不影响下面的循环，就需要将这样一个字符从缓冲区删除
                }else{
                    c = '\n';
                }
            }
            
            //这个条件包含了\r和\r\n的情况
            if(c == '\n'){
                break;
            }
            line.push_back(c);
        }
        return 0;
    }


    

    //从文件描述符读N个字节
    static int ReadN(int fd,size_t len,std::string& output){
        output.clear();
        char c = '\0';
        for(size_t i = 0;i < len;++i){
            recv(fd,&c,1,0);
            output.push_back(c);
        }
        return 0;
    }

    //判断一个字符串所指的文件是否是目录
    static bool IsDir(const std::string& file_path){
        struct stat buf;
        if(stat(file_path.c_str(),&buf) < 0)
            perror("stat");
        if(S_ISDIR(buf.st_mode))
            return true;
        else
            return false;
        // return boost::filesystem::is_directory(file_path);        
    }

    //从文件中读取全部内容到 std::string中
    static int ReadAll(const std::string& file_path,std::string& output){
        std::ifstream file(file_path.c_str());
        std::cout << file_path << std::endl;
        if(!file.is_open()){
            LOG(ERROR) << "Open file error! file_path=" << file_path << "\n";
            return -1;
        }

        //seekg 调整文件指针的位置，此处是将文件指针调整到文件末尾
        file.seekg(0,file.end);
        //tellg 查询当前文件指针的位置，返回值就是文件指针位置相对于文件起始位置的偏移量
        int length = file.tellg();
        //为了从头读取文件，需要把文件指针设置到开头位置
        file.seekg(0,file.beg);        
        //读取完整的文件内容
        output.resize(length);//重新设定缓冲区的大小，大小为length
        file.read(const_cast<char*>(output.c_str()),length);

        file.close(); //这里也可以不调用close，C++会调用ifstream对象的析构函数
        return 0;
    }

    //从文件描述符中读取所有数据到 std::string中
    static int ReadAll(int fd,std::string& output){
        while(true){  
            char buf[1024] = {0};
            ssize_t read_size = read(fd,buf,sizeof(buf)-1);
            if(read_size < 0){
                perror("read");
                return -1;
            }
            if(read_size == 0){
                //表示文件读完了
                return 0; 
            }
            
            buf[read_size] = '\0';
            output += buf;
        }
        return 0;
    }    
};

class StringUtil{
public:
    typedef std::unordered_map<std::string,std::string> UrlParam;
    static int ParseUrlParam(const std::string& input,UrlParam& output){
        //1.先按照取地址符号切分成若干个kv
        std::vector<std::string> params;
        boost::algorithm::split(params,input,boost::algorithm::is_any_of("&"));
        //2.再针对每一个kv，按照=切分，放到输出结果中
        for(auto item : params){
            std::vector<std::string> kv;
            boost::algorithm::split(kv,item,boost::algorithm::is_any_of("="));
            if(kv.size() != 2){
                //说明该参数非法
                LOG(WARNING) << "kv format error! item=" << item << "\n";
                continue;
            }
            output[kv[0]] = kv[1];
        }
        return 0;
    }

    static int ParseCookieParam(const std::string& input,UrlParam& output){
        //1.先按照;切分成若干个kv
        std::vector<std::string> params;
        boost::algorithm::split(params,input,boost::algorithm::is_any_of(";"));
        //2.再针对每一个kv，按照=切分，放到输出结果中
        for(size_t i = 0;i < params.size();++i){
            std::vector<std::string> kv;
            boost::algorithm::split(kv,params[i],boost::algorithm::is_any_of("="));
            if(kv.size() != 2){
                //说明该参数非法
                LOG(WARNING) << "kv format error! item=" << params[i] << "\n";
                continue;
            }

            //这是因为每个cookie之前都有一个空格,而第一个cookie又没有空格
            std::string key;
            if(i == 0)
                key = kv[0];
            else
                key = kv[0].substr(1);

            output[key] = kv[1];
        }
        return 0;
    }

    static unsigned char ToHex(unsigned char x)   
    {   
        return  x > 9 ? x + 55 : x + 48;   
    }  

    static unsigned char FromHex(unsigned char x)   
    {   
        unsigned char y;  
        if (x >= 'A' && x <= 'Z')
            y = x - 'A' + 10;
        else if (x >= 'a' && x <= 'z')
            y = x - 'a' + 10;
        else if (x >= '0' && x <= '9')
            y = x - '0';
        else
            assert(0);                    
        return y;  
    }  

    static std::string UrlDecode(const std::string& str)  
    {  
        std::string strTemp = "";  
        size_t length = str.length();  
        for (size_t i = 0; i < length; i++)  
        {  
            if (str[i] == '+') 
                strTemp += ' ';  
            else if (str[i] == '%'){  
                assert(i + 2 < length);  
                unsigned char high = FromHex((unsigned char)str[++i]);  
                unsigned char low = FromHex((unsigned char)str[++i]);  
                strTemp += high*16 + low;  
            }else 
                strTemp += str[i];  
        }  
        return strTemp;  
    }  

};
